<?php

namespace Aspic;

/**
 * Contains usefull array methods
 *
 * @Author Mallory Dessaintes <mdessaintes@gmail.com>
 * @version 1.0
 */
class UArray
{

    /**
     * Return specifies keys from a array
     */
    public static function getKeys($keys, array $src, $keysSeparator = ';')
    {
        if (!is_array($keys)) {
            $arr = array();
            $keys = explode($keysSeparator, $keys);
        }

        foreach ($keys as $k) {
            $arr[$k] = Util::getIfIsSet($src[$k]);
        }

        return $arr;
    }

    /**
     * Search in $arr lines if there is one line that contains an array (object if $propertySearch = true)
     * with index(property) $index and value $value
     */
    public static function inLines(array $arr, $value, $index, $propertySearch = false)
    {
        return (bool)static::inLinesSearch($arr, $value, $index, $propertySearch);
    }

    /**
     * Return an array containing for each line the content of $arr[$i][$key]
     *
     * @param bool $preserveKey If true, the returned array keep the keys of the original
     */
    public static function getLinesIndex(array $arr, $keyToKeep, $preserveKeys = false, $propertySearch = false)
    {
        $res = array();

        foreach ($arr as $key => $value) {
            if ($propertySearch) {
                $valueToKeep = $value->{$keyToKeep};
            } else {
                $valueToKeep = $value[$keyToKeep];
            }

            if ($preserveKeys) {
                $res[$key] = $valueToKeep;
            } else {
                $res[] = $valueToKeep;
            }
        }

        return $res;
    }

    /**
     * Search in $arr lines if there is one line that contains an array (object if $propertySearch = true)
     * with index(property) $index and value $value AND return it's position
     *
     * @param bool $propertySearch Search index in property (instead of array index)
     * @param bool $sezrchAll Search all occurences instead of returning first
     * @return int|bool The position of the searched element in the array or false if not found
     */
    public static function inLinesSearch(array $arr, $value, $index, $propertySearch = false, $searchAll = false)
    {
        $all = array();

        foreach ($arr as $k => $row) {
            if (
                (!$propertySearch AND $row[$index] == $value) OR ($propertySearch AND $row->{$index} == $value)
            ) {
                if ($searchAll) {
                    $all[] = $k;
                } else {
                    return $k;
                }
            }
        }

        if ($searchAll) {
            return $all;
        } else {
            return false;
        }
    }

    /**
     * Insert a element ($toInsert) at the $offset position
     *
     * @param int $offset The position where to insert $toInsert
     * $offset is NOT the key of the array but the position in the array
     * (that makes sens when array keys are numeric and string together)
     *
     * @param string $key Define the key of the element to insert (default: next numeric index)
     */
    public static function insertAtOffset(array $arr, $toInsert, $offset, $key = null)
    {
        /*		 * * get the start of the array ** */
        $start = array_slice($arr, 0, $offset);
        /*		 * * get the end of the array ** */
        $end = array_slice($arr, $offset);
        /*		 * * add the new element to the array ** */
        $key = ($key) ? $key : count($start);
        $start[$key] = $toInsert;
        /*		 * * glue them back together and return ** */
        $merge = array_merge($start, $end);

        return $merge;
    }

    /**
     * Insert a element ($toInsert) at the $index position
     *
     * @param array $arr The array where to insert $toInsert. It MUST be only numeric indexed
     * (otherwise, the index position will be wrong)
     */
    public static function insertAtIndex(array $arr, $toInsert, $index)
    {
        $nbEls = count($arr);

        if ($index >= $nbEls) {
            $arr[$index] = $toInsert;
        } else {
            for ($i = $nbEls; $i > $index; --$i) {
                $arr[$i] = $arr[$i - 1];
            }

            $arr[$index] = $toInsert;
        }

        return $arr;
    }

    /**
     * Merge two or more array recursivelly without grouping values that have same key in a new array like array_merge_recursive do
     */
    public static function mergeRecursiveSimple()
    {
        if (func_num_args() < 2) {
            trigger_error(__FUNCTION__ . ' needs two or more array arguments', E_USER_WARNING);
            return;
        }
        $arrays = func_get_args();
        $merged = array();
        while ($arrays) {
            $array = array_shift($arrays);
            if (!is_array($array)) {
                trigger_error(__FUNCTION__ . ' encountered a non array argument', E_USER_WARNING);
                return;
            }
            if (!$array)
                continue;
            foreach ($array as $key => $value)
                if (is_string($key))
                    if (is_array($value) && array_key_exists($key, $merged) && is_array($merged[$key]))
                        $merged[$key] = call_user_func('static::' . __FUNCTION__, $merged[$key], $value);
                    else
                        $merged[$key] = $value;
                else
                    $merged[] = $value;
        }

        return $merged;
    }

    /**
     * Get a reference to an array value from its "path"
     *
     * @param array $arr The base array like array('a' => array('b' => array(1, 2, 3)))
     * @param array $arrPath The array path to get the value like array('a', 'b') will return array(1, 2, 3);
     *
     * @return mixed Return the value or null if it does not exists
     */
    public static function &getSubArrayRefFromPath(&$arr, array $arrPath)
    {
        if (!is_array($arrPath) AND !$arr instanceof \ArrayAccess) {
            throw new Exception('$arrPath is not an array');
        } else {
            $el = & $arr;

            foreach ($arrPath as $arrKey) {
                $el = & $el[$arrKey];
            }

            return $el;
        }
    }

    /**
     * Same as getSubArrayRefFromPath excepts that key path like an html input name. ex: subscription[login] or hobbies[]
     *
     * @param string $html
     *
     * @return mixed Return the value or null if it does not exists
     */
    public static function &getSubArrayRefFromHtmlPath(&$arr, $htmlPath)
    {
        $keys = static::getArrayKeysFromHtmlPath($htmlPath);
        $el = & static::getSubArrayRefFromPath($arr, $keys);

        return $el;
    }

    /**
     * Return an array of keys like array('a', 'b', 'c') from html path like "a[b][c]"
     */
    public static function getArrayKeysFromHtmlPath($htmlPath)
    {
        $path = UString::removeLast($htmlPath, '[]');

        $explode = explode('[', $path, 2);

        $keys = array();

        if ($explode[0] == $path) {
            $keys[] = $path;
        } else {
            $pathNext = '[' . $explode[1]; // The first [ was remove by explode
            preg_match_all('#\[([^\[\]]+)\]#i', $pathNext, $matches);

            $keys[0] = $explode[0];
            $keys = array_merge($keys, $matches[1]);
        }

        return $keys;
    }

    /**
     * Reverse of getArrayKeysFromHtmlPath => return "a[b][c]" from array('a', 'b', 'c')
     */
    public static function getHtmlPathFromArrayKeys(array $keys)
    {
        if (count($keys) == 1) {
            return $keys[0];
        } else {
            $result = $keys[0];

            for ($i = 1; $i < count($keys); ++$i) {
                $result .= '[' . $keys[$i] . ']';
            }

            return $result;
        }
    }

    /**
     * Return true if $arr is an array and if there is at least one element in it
     *
     * @deprecated Use isNotEmptyArray instead
     */
    public static function countArray($data)
    {
        return static::isNotEmptyArray($data);
    }

    /**
     * Return true if $arr is an array and if there is at least one element in it
     */
    public static function isNotEmptyArray($data)
    {
        return (is_array($data) && count($data));
    }

    public static function isEmptyArray($data)
    {
        return is_array($data) AND !count($data);
    }

    /**
     * Convert an associative multiple dimensions array to an array with only one dimensions like this :
     * array('a' => array('b' => 'c')) => array('a.b' => 'c') The "." is the $separator variable
     */
    public static function multiDimToOneDim(array $arr, $separator = '.', $arrPrefix = array())
    {
        $data = array();

        foreach ($arr as $k => $v) {
            if (is_array($v)) {
                $newArrPrefix = $arrPrefix;
                $newArrPrefix[] = $k;

                $arrayData = static::multiDimToOneDim($v, $separator, $newArrPrefix);

                $data = array_merge($data, $arrayData);
            } else {
                $arrVarName = $arrPrefix;
                $arrVarName[] = $k;

                $data[implode($separator, $arrVarName)] = $v;
            }
        }

        return $data;
    }

    /**
     * Sort a 2 dimensions array based on one element index
     */
    public static function sortByElementKey(array $arr, $index)
    {
        $sort = array();
        //préparation d'un nouveau tableau basé sur la clé à trier
        foreach ($arr as $key => $val) {
            if (is_object($val)) {
                $sort[$key] = $val->{$index};
            } else {
                $sort[$key] = $val[$index];
            }
        }

        asort($sort);
        //formation du nouveau tableau trié selon la clé
        $output = array();

        foreach ($sort as $key => $val) {
            if (is_string($key)) {
                $output[$key] = $arr[$key];
            } else {
                $output[] = $arr[$key];
            }
        }

        return $output;
    }

    /**
     * Convert stdclass, aspic stdData into array
     */
    public static function toArray($object, $acceptNull = false)
    {
        if ($object instanceof \stdclass) {
            $arr = (array)$object;
        } elseif (is_array($object)) {
            $arr = $object;
        } elseif ($object instanceof ArrayConvertibleInterface) {
            $arr = $object->toArray();
        } elseif ($acceptNull AND is_null($object)) {
            return array();
        } else {
            throw new Exception('Cannot convert given object to array');
        }

        return $arr;
    }

    /**
     * Return given array with its numeric keys / values pairs only
     *
     * @param string $keyType => 'numeric' or 'string'
     */
    public static function filterByKeyType(array $arr, $keyType = 'integer')
    {
        $out = array();

        foreach ($arr as $k => $v) {
            if (gettype($k) == $keyType) {
                $out[$k] = $v;
            }
        }

        return $out;
    }

    /**
     * Check if $arr has at least one numeric numeric index
     *
     * @param array $arr
     */
    public static function hasNumericIndex(array $arr)
    {
        return count(array_filter(array_keys($arr), 'is_numeric')) > 0;
    }

    /**
     * Return random value from given array (works only with numeric keys)
     */
    public static function randValue(array $arr)
    {
        $key = array_rand($arr);

        return $arr[$key];
    }

    /**
     * Remove the array row with $value
     *
     * @param array $arr
     * @param type $value
     * @param bool $keepKeys Define if keys of the new array will be the same of first array of
     * re-indexed from 0 (numeric indexed array)
     * @param bool $caseSensitive Case sensitive check when string values
     * @return array
     */
    public static function removeFromValue(array $arr, $value, $keepKeys = true, $caseSensitive = true)
    {
        $new = array();

        foreach ($arr as $k => $arrV) {
            if (
                (is_string($value) AND !UString::equals($arrV, $value, $caseSensitive)) OR (!is_string($value) AND $value != $arrV)
            ) {
                if ($keepKeys) {
                    $new[$k] = $arrV;
                } else {
                    $new[] = $arrV;
                }
            }
        }

        return $new;
    }

    /**
     * Remove an element of the array from its index
     *
     * @param array $arr
     * @param type $index
     * @param type $reOrder
     * @return array
     */
    public static function removeFromIndex(array &$arr, $index, $reOrder = true)
    {
        if (!$reOrder) {
            $new = $arr;

            unset($new[$index]);
        } else {
            $new = array();

            foreach ($arr as $k => $arrV) {
                if ($k != $index) {
                    $new[] = $arrV;
                }
            }
        }

        return $new;
    }

    public static function getObjectNotationString($objectNotation)
    {
        if (is_string($objectNotation)) {
            return $objectNotation;
        } else {
            return implode('.', $objectNotation);
        }
    }

    /**
     * Divise un tableau en $splitNb parties
     *
     * @param array $arr
     * @param $splitNb
     * @return array
     */
    public static function splitArray(array $arr, $splitNb)
    {
        $listlen = count($arr);
        $partlen = floor($listlen / $splitNb);
        $partrem = $listlen % $splitNb;
        $partition = array();
        $mark = 0;

        for ($px = 0; $px < $splitNb; $px++) {
            $incr = ($px < $partrem) ? $partlen + 1 : $partlen;
            $partition[$px] = array_slice($arr, $mark, $incr);
            $mark += $incr;
        }

        return $partition;
    }

    public static function getFirstNotNull(array $arr)
    {
        return current(array_filter($arr));
    }

}